package com.pig4cloud.pig.ads.gdt.service.impl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.google.api.client.util.Lists;
import com.pig4cloud.pig.ads.gdt.service.GdtAccesstokenService;
import com.pig4cloud.pig.ads.gdt.service.GdtAdvertiserService;
import com.pig4cloud.pig.ads.pig.mapper.gdt.GdtAdvFundsMapper;
import com.pig4cloud.pig.ads.pig.mapper.gdt.GdtAdvertiserMapper;
import com.pig4cloud.pig.ads.service.AdAccountService;
import com.pig4cloud.pig.ads.service.AdUserAdverService;
import com.pig4cloud.pig.ads.service.AdvService;
import com.pig4cloud.pig.ads.service.AdvertiserService;
import com.pig4cloud.pig.ads.utils.HttpUtils;
import com.pig4cloud.pig.ads.utils.OEHttpUtils;
import com.pig4cloud.pig.api.entity.AdAccount;
import com.pig4cloud.pig.api.entity.Advertising;
import com.pig4cloud.pig.api.entity.ResponseBean;
import com.pig4cloud.pig.api.gdt.dto.GdtAdvertiserDailyReportReq;
import com.pig4cloud.pig.api.gdt.dto.GdtRelations;
import com.pig4cloud.pig.api.gdt.entity.GdtAdvFunds;
import com.pig4cloud.pig.api.gdt.vo.GdtAdvertiserVo;
import com.pig4cloud.pig.ads.clickhouse3399.mapper.GdtAdvertiserDailyReportMapper;
import com.pig4cloud.pig.api.gdt.vo.GdtAuthCodeReqVo;
import com.pig4cloud.pig.api.util.Constants;
import com.pig4cloud.pig.api.util.JsonUtil;
import com.pig4cloud.pig.api.util.MapUtils;
import com.pig4cloud.pig.api.vo.AdAccountAgentVo.Advertiser;
import com.pig4cloud.pig.common.core.constant.enums.PlatformTypeEnum;
import com.pig4cloud.pig.common.core.exception.BusinessException;
import com.pig4cloud.pig.common.core.util.R;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pig.api.entity.AdUserAdver;
import com.pig4cloud.pig.api.gdt.entity.GdtAccesstoken;
import com.pig4cloud.pig.api.gdt.entity.GdtAdvertiser;
import com.pig4cloud.pig.common.security.util.SecurityUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
@RequiredArgsConstructor
public class GdtAdvertiserServiceImpl  extends ServiceImpl<GdtAdvertiserMapper, GdtAdvertiser> implements GdtAdvertiserService {

	private final AdvService advService;

	private final GdtAccesstokenService gdtAccesstokenService;

	private final AdUserAdverService adUserAdverService;

	private final GdtAdvertiserMapper gdtAdvertiserMapper;

	private final GdtAdvFundsMapper gdtAdvFundsMapper;

	private final GdtAdvertiserDailyReportMapper gdtAdvertiserDailyReportMapper;

	private final AdAccountService adAccountService;

	private final AdvertiserService advertiserService;

	private final StringRedisTemplate stringRedisTemplate;

	/**
	 * @可授权的账号列表
	 * @return
	 */
	@Override
	public List<String> getAuthAdvList() {
		List<String> allList = new ArrayList<>();
		//自身含有的账号
		List<AdUserAdver> list = adUserAdverService.list(Wrappers.<AdUserAdver>query().lambda().eq(AdUserAdver::getUserId, SecurityUtils.getUser().getId())
				.eq(AdUserAdver::getStatus, 0));
		if(list == null || list.size() == 0) {
			return allList;
		}
		//转list
		List<String> adList=list.stream().map(AdUserAdver::getAdvertiserId).collect(Collectors.toList());
		//排除普通账号，查询出的都是管家账户
		List<String> housekeeperList = gdtAccesstokenService.list(Wrappers.<GdtAccesstoken>query().lambda().in(GdtAccesstoken::getAccountId, adList)).stream().map(GdtAccesstoken::getAccountId).collect(Collectors.toList());
		if(housekeeperList == null || housekeeperList.size() == 0) {
			return allList;
		}

		//账号管家名下的所有普通账号
		List<String> advList = gdtAdvertiserMapper.selectList(Wrappers.<GdtAdvertiser>query().lambda().in(GdtAdvertiser::getHousekeeper, adList)).stream().map(GdtAdvertiser::getAccountId).collect(Collectors.toList());

		//管家账号
		//allList.addAll(housekeeperList);
		allList.addAll(advList);

		return allList;
	}

	@Override
	public R pagedAdversWithFund(Page page, String searchStr) {
		List<Advertising> list = new ArrayList<>();
		Integer id = SecurityUtils.getUser().getId();

		List<String> adList = advService.getOwnerAdv(id, PlatformTypeEnum.GDT.getValue());
		if(adList == null || adList.size() == 0) {
			return R.ok(list);
		}

		LambdaQueryWrapper<GdtAdvertiser> lambda = new LambdaQueryWrapper<>();

		lambda.and(wrapper -> wrapper.in(GdtAdvertiser::getAccountId, adList).or().in(GdtAdvertiser::getHousekeeper, adList));
		if (StringUtils.isNotBlank(searchStr))
			lambda.and(wrapper -> wrapper.like(GdtAdvertiser::getAccountId, searchStr));

		IPage result = this.page(page, lambda);
		List<GdtAdvertiser> adverList = result.getRecords();

		List<GdtAdvertiserVo> voList = adverList.stream().map(item -> {
			GdtAdvertiserVo vo = new GdtAdvertiserVo();
			vo.setAccountId(item.getAccountId());
			vo.setCorporationName(item.getCorporationName());

			GdtAdvFunds foundRecord = gdtAdvFundsMapper.selectById(item.getAccountId());
			if (foundRecord == null)
				return vo;

			vo.setDailyBudget(foundRecord.getDailyBudget());
			vo.setAlertFund(foundRecord.getAlertFund());
			vo.setOrderDailyBudget(foundRecord.getOrderDailyBudget());

			String foundsStr = foundRecord.getFunds();
			if (StringUtils.isNotBlank(foundsStr)){
				JSONArray foundArr = JSON.parseArray(foundsStr);
				BigDecimal cash = BigDecimal.ZERO;
				BigDecimal shared = BigDecimal.ZERO;
				BigDecimal gift = BigDecimal.ZERO;
				for (int i=0; i< foundArr.size(); i++){
					JSONObject foundItem = foundArr.getJSONObject(i);
					String foundType = foundItem.getString("fund_type");
					if (StringUtils.isBlank(foundType))
						continue;

					switch (foundType){
						case "FUND_TYPE_CASH":
							cash = foundItem.getBigDecimal("balance");
							break;
						case "FUND_TYPE_GIFT":
							gift = foundItem.getBigDecimal("balance");
							break;
						case "FUND_TYPE_SHARED":
							shared = foundItem.getBigDecimal("balance");
							break;
						default:
							break;
					}
				}

				vo.setCashBalance(cash);
				vo.setSharedBalance(shared);
				vo.setGiftBalance(gift);
				vo.setTotalBalance(
						(cash ==null ? BigDecimal.ZERO : cash)
								.add(shared==null ? BigDecimal.ZERO :shared)
								.add(gift==null ? BigDecimal.ZERO :gift)
				);
			}

			return vo;
		}).collect(Collectors.toList());

		IPage<GdtAdvertiserVo> voPage = new Page<GdtAdvertiserVo>(result.getCurrent(),result.getSize(),result.getTotal());
		voPage.setRecords(voList);
		return R.ok(voPage);
	}

	@Override
	public R pagedDailyReport(GdtAdvertiserDailyReportReq req) {
		if (StringUtils.isNotBlank(req.getAccountId())){
			String[] ids = {req.getAccountId()};
			req.setAccountIds(Arrays.asList(ids));
		}else{
			List<GdtAdvertiser> advers = this.curUserAdvers();
			if (advers != null && advers.size()>0){
				List<String> advs = advers.stream().map(adver -> adver.getAccountId()).collect(Collectors.toList());
				req.setAccountIds(advs);
			}else{
				String[] ids = {"0"};
				req.setAccountIds(Arrays.asList(ids));
			}
		}
		return R.ok(gdtAdvertiserDailyReportMapper.selectAdvertiserDailyReport(new Page(req.getCurrent(), req.getSize()), req));
	}

	@Override
	public R changeDailyBudget(String accountId, BigDecimal budget) {
		GdtAdvFunds foundsRecord = gdtAdvFundsMapper.selectById(accountId);
		R result = this.updateBudgetInGdt(accountId, budget);

		if (result.getCode() == 0){
			 foundsRecord.setDailyBudget(budget);
			 gdtAdvFundsMapper.updateById(foundsRecord);
		}
		return result;
	}

	@Override
	public R orderDailyBudget(String accountId, BigDecimal budget) {
		if (StringUtils.isBlank(accountId))
			return R.failed("请提供账户id");
		if (budget == null)
			return R.failed("其提供日预算");

		GdtAdvFunds foundsRecord = gdtAdvFundsMapper.selectById(accountId);
		if (foundsRecord == null)
			return R.failed(String.format("未找到账户[%s]的日预算",accountId));

		Map orderedData = new HashMap();

		Date curDate = new Date();
		orderedData.put("order_time", System.currentTimeMillis());
		orderedData.put("budget", budget);

		foundsRecord.setOrderDailyBudget(JSON.toJSONString(orderedData));

		if (SqlHelper.retBool(gdtAdvFundsMapper.updateById(foundsRecord))){
			return R.ok();
		}else{
			return R.failed();
		}
	}


	private R updateBudgetInGdt(String accountId, BigDecimal budget){
		if (StringUtils.isBlank(accountId))
			return R.failed("请提供广告账户");
		if (budget == null || budget.compareTo(BigDecimal.ZERO) == -1){
			return R.failed("未提供预算，或预算小于0");
		}

		Map<String, Object> reqData = new HashMap<>();
		reqData.put("account_id", accountId);
		reqData.put("daily_budget", budget.multiply(BigDecimal.valueOf(100)));

		String resultStr = OEHttpUtils.doPost("https://api.e.qq.com/v1.3/advertiser/update" + getUrlTail(accountId), reqData, null);

		//结果为null，当做失败
		if (StringUtils.isBlank(resultStr))
			return R.failed("第三方更新失败:resultStr is null");

		JSONObject obj = JSON.parseObject(resultStr);
		if (!obj.getInteger("code").equals(0)){	//同步失败
			String msg = obj.getString("message_cn");
			return R.failed("第三方更新失败:" + msg);
		}

		//若同步成功	跟更新状态到数据库
		return R.ok(null,"第三方更新成功");
	}

	private String getUrlTail(String accountId){

		//任意去一个 accountId去拿token
		Map<String, String> map = gdtAccesstokenService.fetchAccesstoken(accountId);
		String gdtToken = map.get("access_token");

		//String gdtToken = "4b7eff592ce3abcd4960b378674e6595";

		int nonce = RandomUtils.nextInt();
		long tsp = System.currentTimeMillis() / 1000;
		String tail = String.format("?access_token=%s&timestamp=%d&nonce=%d", gdtToken, tsp, nonce);
		return tail;
	}

	@Override
	public List<GdtAdvertiser> curUserAdvers(){
		Integer id = SecurityUtils.getUser().getId();
		List<String> adList = advService.getOwnerAdv(id, PlatformTypeEnum.GDT.getValue());

		if(adList == null || adList.size() == 0) {
			return null;
		}

		LambdaQueryWrapper<GdtAdvertiser> lambda = new LambdaQueryWrapper<>();
		lambda.and(wrapper -> wrapper.in(GdtAdvertiser::getAccountId, adList).or().in(GdtAdvertiser::getHousekeeper, adList));

		List<GdtAdvertiser> advs = this.baseMapper.selectList(lambda);
		return advs;
	}

	@Override
	public void saveUpdateList(List<GdtAdvertiser> list){
		if(CollectionUtils.isNotEmpty(list)) {
			this.saveOrUpdateBatch(list, 1000);
		}
	}


	@Override
	public List<Advertiser> saveAuth(GdtAuthCodeReqVo authVo) throws Exception{
		final List<Advertiser> advertiserList = new ArrayList<>();

		Map<String, String> data = new HashMap<String, String>();
		data.put("client_id", authVo.getClient_id());
		data.put("client_secret", authVo.getClient_secret());
		data.put("grant_type", "authorization_code");
		data.put("authorization_code", authVo.getAuthorization_code());
		data.put("redirect_uri", urlEncode(authVo.getRedirect_uri()));//应用回调地址，当 grant_type=authorization_code 时，redirect_uri 为必传参数，仅支持 http 和 https，不支持指定端口号，且传入的地址需要与获取 authorization_code 时，传入的回调地址保持一致

		String urlstr = "https://api.e.qq.com/oauth/token?" + MapUtils.queryString(data);
		log.info("urlstr={}", urlstr);

		String resultStr = HttpUtils.doGet(urlstr, null);

		log.info("resultStr={}", resultStr);
		ResponseBean resBean = JSON.parseObject(resultStr, ResponseBean.class);
		if(resBean != null && "0".equals(resBean.getCode())){
			JSONObject jsonObj = resBean.getData();
			String authinfo = jsonObj.getString("authorizer_info");

			GdtAccesstoken votoken;

			try {
				votoken = JsonUtil.toSnakeObject(JSON.toJSONString(authVo), GdtAccesstoken.class);
			} catch (IOException e) {
				log.error("驼峰转换失败!");
				throw new Exception("驼峰转换失败!");
			}

			GdtAccesstoken authtoken = JSON.parseObject(authinfo, GdtAccesstoken.class);

			log.info("====>jsonObj={}", jsonObj.toJSONString());

			GdtAccesstoken token = JSON.toJavaObject(jsonObj, GdtAccesstoken.class);
			copyPropertiesIgnoreNull(authtoken, token);
			log.info("=====>token1={}", JSON.toJSONString(token));
			copyPropertiesIgnoreNull(votoken, token);
			log.info("=====>token2={}", JSON.toJSONString(token));

			token.setTime(BigInteger.valueOf(System.currentTimeMillis()/1000));

			gdtAccesstokenService.saveOrUpdate(token);

			QueryWrapper<AdAccount> wrapper = new QueryWrapper<>();
			wrapper.eq("advertiser_id",token.getAccountId());
			wrapper.eq("is_delete",0);
			int num = adAccountService.count(wrapper);
			if(num <= 0) {
				// 新增广告账户表
				AdAccount adAccount = new AdAccount();
				adAccount.setMediaCode(PlatformTypeEnum.GDT.getValue());
				adAccount.setMediaName(PlatformTypeEnum.GDT.getDescription());
				adAccount.setAdvertiserId(token.getAccountId());
				adAccount.setAdvertiserName("宁德创游网络科技有限公司");
				adAccount.setHousekeeper(token.getAccountId());
				adAccount.setIsDelete(0);
				adAccount.setCreateTime(new Date());
				adAccount.setUpdateTime(new Date());
				adAccountService.save(adAccount);
			}
			//调用管家账户查询下挂账号
			saveChildAccount(token.getAccountId(),advertiserList);
			return advertiserList;
		} else {
			throw new BusinessException(resBean.getMessage());
		}
	}

	private void saveChildAccount(String housekeeper, List<Advertiser> advertiserList) {

		Map<String, String> data = gdtAccesstokenService.findGdtCommonData(housekeeper);
		List<Map> lists = Lists.newArrayList();
		data.put("page", "1");

		data.put("page_size", "100");

		String urlstr = "https://api.e.qq.com/v1.3/business_manager_relations/get?" + MapUtils.queryString(data);

		String resultStr = HttpUtils.doGet(urlstr, null);

		List<GdtAdvertiser> gdtAdList =new ArrayList<>();
		log.info("获取管家账户下的所有账户resultStr=={}, data={}", resultStr, data);
		ResponseBean resBean = JSON.parseObject(resultStr, ResponseBean.class);
		if(resBean != null && "0".equals(resBean.getCode())){
			JSONObject jsonObject = resBean.getData();
			String listStr = jsonObject.getString("list");
			List<GdtRelations> list = JSONObject.parseArray(listStr,  GdtRelations.class);
			for(GdtRelations adInfo: list){
				Map map = new HashMap();
				map.put("advertiser_id", adInfo.getAccount_id());
				map.put("advertiser_name", adInfo.getCorporation_name());
				map.put("housekeeper",housekeeper);
				map.put("deleted",0);
				lists.add(map);
				log.info("====>adInfo={}", JSON.toJSONString(adInfo));
				String accountId = adInfo.getAccount_id();
				//转化驼峰
				GdtAdvertiser snake = new GdtAdvertiser();
				try {
					snake = JsonUtil.toSnakeObject(JSON.toJSONString(adInfo), GdtAdvertiser.class);
				} catch (IOException e) {
					log.error("驼峰转换失败!");
				}

				log.info("====>snake={}", JSON.toJSONString(snake));

				//获取资金账户信息
				GdtAdvertiser adv = gdtAdvertiserMapper.selectById(accountId);
				if(adv!=null) {
					BeanUtils.copyProperties(snake, adv);
					adv.setHousekeeper(housekeeper);
					gdtAdList.add(adv);
				}else {
					snake.setHousekeeper(housekeeper);
					gdtAdList.add(snake);
				}
				//TODO  广点通账号名称具体怎么展示
				stringRedisTemplate.opsForValue().set(Constants.AD_REDIS_AD_NAME_KEY_PRIX_ + accountId, adInfo.getCorporation_name()+"(" + accountId + ")");

			}
		}

		List<AdAccount> adAccountList = Lists.newArrayList();

		QueryWrapper<AdAccount> wrapperAcc = new QueryWrapper<>();
		wrapperAcc.eq("media_code",PlatformTypeEnum.GDT.getValue());
		wrapperAcc.eq("is_delete",0);
		// 头条已经加挂的广告账户
		List<AdAccount> accountsList = adAccountService.list(wrapperAcc);

		for (GdtAdvertiser advertising : gdtAdList){
			advertiserList.add(new Advertiser(advertising.getAccountId(), advertising.getCorporationName()));
			List<AdAccount> accountsArr = accountsList.stream().filter(v -> v.getAdvertiserId().equals(advertising.getAccountId())).collect(Collectors.toList());
			if (CollectionUtil.isNotEmpty(accountsArr)){
				AdAccount adAccount = accountsArr.get(0);
				adAccount.setAdvertiserName(advertising.getCorporationName());
				adAccount.setHousekeeper(housekeeper);
				adAccount.setEffectiveStatus(1);
				adAccount.setUpdateTime(new Date());
				adAccountList.add(adAccount);
			}else {
				AdAccount adAccount = new AdAccount();
				adAccount.setMediaCode(PlatformTypeEnum.GDT.getValue());
				adAccount.setMediaName(PlatformTypeEnum.GDT.getDescription());
				adAccount.setAdvertiserId(advertising.getAccountId());
				adAccount.setAdvertiserName(advertising.getCorporationName());
				adAccount.setHousekeeper(housekeeper);
				adAccount.setIsDelete(0);
				adAccount.setCreateTime(new Date());
				adAccount.setUpdateTime(new Date());
				adAccountList.add(adAccount);
			}
		}
		List<String> ids = adAccountList.stream().map(AdAccount::getAdvertiserId).collect(Collectors.toList());
		accountsList.stream().filter(a-> housekeeper.equals(a.getHousekeeper()) && !ids.contains(a.getAdvertiserId())&& !housekeeper.equals(a.getAdvertiserId())).forEach(b->{
			b.setEffectiveStatus(2);
			b.setUpdateTime(new Date());
			adAccountList.add(b);
			Map<String, String> map = new HashMap<>();
			map.put("housekeeper",housekeeper);
			map.put("deleted","1");
			map.put("advertiser_id",b.getAdvertiserId());
			map.put("advertiser_name",b.getAdvertiserName());
			lists.add(map);
		});
		if (CollectionUtil.isNotEmpty(adAccountList)){
			//批量插入
			adAccountService.saveUpdateList(adAccountList);
		}
		if (CollectionUtil.isNotEmpty(lists)) {
			advertiserService.saveAdvertiser(lists, PlatformTypeEnum.GDT.getValue());
		}
		if (CollectionUtil.isNotEmpty(gdtAdList)) {
			this.saveUpdateList(gdtAdList);
		}
	}

	private String urlEncode(String str) {
		try {
			return URLEncoder.encode(str, "UTF-8");
		}
		catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	private void copyPropertiesIgnoreNull(Object src, Object target){
		BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
	}

	private String[] getNullPropertyNames (Object source) {
		final BeanWrapper src = new BeanWrapperImpl(source);
		java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

		Set<String> emptyNames = new HashSet<String>();
		for(java.beans.PropertyDescriptor pd : pds) {
			Object srcValue = src.getPropertyValue(pd.getName());
			if (srcValue == null) {
				emptyNames.add(pd.getName());
			}
		}
		String[] result = new String[emptyNames.size()];
		return emptyNames.toArray(result);
	}
}
